//
//  tdl/sys/file.h
//  Created by tommy on 13-7-8.
//

#ifndef tdl_sys_file_h
#define tdl_sys_file_h

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
#include "tdlexcept.h"
#include "sys/executor.h"
#include "mem/small_buffer.h"
using namespace tdl;

namespace tdl {
///////////////
enum eFileType
{
	FILE_TYPE_FIFO		= DT_FIFO,
	FILE_TYPE_DIR		= DT_DIR,
	FILE_TYPE_COMM		= DT_REG,
	FILE_TYPE_SOFTLINK	= DT_LNK,
	FILE_TYPE_SOCKET	= DT_SOCK
};

struct file_info
{
	eFileType		type;
	std::string		path;
	std::string		name;
	size_t			size;

	time_t			atime;
	time_t			mtime;
public:
	void set(eFileType t, const std::string& pt, const std::string& nm, size_t s, time_t at, time_t mt)
	{
		type = t;
		path = pt;
		name = nm;
		size = s;
		atime = at;
		mtime = mt;
	}
};

struct sort_fileinfo_policy_by_name
{
	bool operator()(const file_info& a, const file_info& b)
	{ return a.name < b.name; }
};

struct sort_fileinfo_policy_by_size
{
	bool operator()(const file_info& a, const file_info& b)
	{ return a.size < b.size; }
};

struct sort_fileinfo_policy_by_atime
{
	bool operator()(const file_info& a, const file_info& b)
	{ return a.atime < b.atime; }
};

struct sort_fileinfo_policy_by_mtime
{
	bool operator()(const file_info& a, const file_info& b)
	{ return a.mtime < b.mtime; }
};

struct select_file_all
{
	bool operator()(const file_info&, void*)
	{ return true; }
};

void parse_file_path_name(eFileType ft, const std::string& fpn, std::string& path, std::string& name)
{
	int fpn_len = fpn.length();
	int pos = fpn.find_last_of("/");
	if(ft == FILE_TYPE_DIR)
	{
		name = "";
		path = (fpn_len > 1 && pos == fpn_len - 1) ? fpn.substr(0, fpn_len - 1) : fpn;
	}
	else
	{
		if(pos < 0)
		{
			path = ".";
			name = fpn;
		}
		else
		{
			path = fpn.substr(0, pos);
			name = fpn.substr(pos + 1);
		}
	}
}

///////////////
bool get_file_info(const std::string& file_path_name, file_info& fi)
{
	struct stat stbuf;
	if(::stat(file_path_name.c_str(), &stbuf) == -1)
		return false;

	std::string path, name;
	eFileType ft = (eFileType)IFTODT(stbuf.st_mode);
	parse_file_path_name(ft, file_path_name, path, name);

	fi.set(ft, path, name, stbuf.st_size, stbuf.st_atime, stbuf.st_mtime);
	return true;
}

bool is_dir(const std::string& dir_path) throw(file_ex)
{
	struct stat stbuf;

	if(::stat(dir_path.c_str(), &stbuf) == -1)
		THROW2(file_ex, TRACE_INFO() << "stat('" << dir_path << "')");

	return ((stbuf.st_mode & S_IFMT) == S_IFDIR);
}

void frename(const std::string& old_name, const std::string& new_name) throw(file_ex)
{
	if(::rename(old_name.c_str(), new_name.c_str()) < 0)
		THROW2(file_ex, TRACE_INFO() << "rename('" << old_name << "', '" << new_name << "')");
}

///////////////
class directory
{
protected:
	std::string		m_dir_path;
	DIR*			m_dir_fd;

public:
	directory() : m_dir_path(""), m_dir_fd(NULL)
	{
	}

	~directory()
	{
		this->close();
	}

	void close()
	{
		if(m_dir_fd)
		{
			::closedir(m_dir_fd);
			m_dir_fd = NULL;
		}
	}

	void open(const std::string& dir_path, bool create_if_no_exists = true) throw(file_ex)
	{
		if(! is_dir(dir_path))
			THROW(file_ex, dir_path << " is not a directory");

		m_dir_fd = ::opendir(dir_path.c_str());
		if(m_dir_fd == NULL)
			THROW2(file_ex, TRACE_INFO() << "opendir('" << dir_path << "')");
		// create_if_no_exists

		std::string name;
		parse_file_path_name(tdl::FILE_TYPE_DIR, dir_path, m_dir_path, name);
	}

	template <typename Tsort, typename Tselect>
	void read(std::vector<file_info>& deis, Tselect& selor, void* cond = NULL) throw(file_ex)
	{
		if(m_dir_fd == NULL)
			THROW(file_ex, "open a directory first");

		deis.clear();
		struct dirent* dp = ::readdir(m_dir_fd);
		while(dp)
		{
			string name(dp->d_name);

			file_info fi;
			get_file_info(m_dir_path + "/" + name, fi);

			if(selor(fi, cond))
				deis.push_back(fi);

			dp = ::readdir(m_dir_fd);
		}

		Tsort sort_policy_func;
		std::sort(deis.begin(), deis.end(), sort_policy_func);
	}
};

///////////////
}	// namespace tdl

#endif

